Teams AI 库入门

Teams AI 库简化了使用 AI 组件生成智能 Microsoft Teams 应用程序的过程。 它提供用于访问和操作数据的 API,以及一系列用于创建自定义用户界面的控件和组件。

可以轻松地将 Teams AI 库、提示管理和安全审查集成到应用中,并增强用户体验。 它还有助于创建使用 OpenAI API 密钥或 Azure OpenAI 提供 AI 驱动的对话体验的机器人。

初始设置

Teams AI 库基于 Bot Framework SDK 构建,使用其基础知识提供 Bot Framework SDK 功能的扩展。 作为初始设置的一部分,请务必导入 Bot Framework SDK 功能。

注意

处理与通道连接的适配器类是从 Bot Framework SDK 导入的。

示例代码参考

using Microsoft.Teams.AI;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.TeamsFx.Conversation;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600));
builder.Services.AddHttpContextAccessor();

// Prepare Configuration for ConfigurationBotFrameworkAuthentication
var config = builder.Configuration.Get<ConfigOptions>();
builder.Configuration["MicrosoftAppType"] = "MultiTenant";
builder.Configuration["MicrosoftAppId"] = config.BOT_ID;
builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD;

// Create the Bot Framework Authentication to be used with the Bot Adapter.
builder.Services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

// Create the Cloud Adapter with error handling enabled.
// Note: some classes expect a BotAdapter and some expect a BotFrameworkHttpAdapter, so
// register the same adapter instance for all types.
builder.Services.AddSingleton<CloudAdapter, AdapterWithErrorHandler>();
builder.Services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetService<CloudAdapter>());
builder.Services.AddSingleton<BotAdapter>(sp => sp.GetService<CloudAdapter>());

导入 Teams AI 库

@microsoft/teams-ai 中导入所有类以生成机器人并使用 Teams AI 库功能。

示例代码参考

// import Teams AI library
import {
    AI,
    Application,
    ActionPlanner,
    OpenAIModerator,
    OpenAIModel,
    PromptManager,
    TurnState
} from '@microsoft/teams-ai';
import { addResponseFormatter } from './responseFormatter';
import { VectraDataSource } from './VectraDataSource';

Create AI 组件

将 AI 功能添加到现有应用或新的 Bot Framework 应用。

OpenAIModel:OpenAIModel 类提供了一种访问 OpenAI API 或任何其他服务的方法,该服务遵循 OpenAI REST 格式。 它与 OpenAI 和 Azure OpenAI 语言模型兼容。

提示管理器:提示管理器管理提示创建。 它调用函数,并将代码中的注入到提示符中。 它会自动将聊天状态和用户状态复制到提示中。

ActionPlanner:ActionPlanner 是调用大型语言模型 (LLM) 的main组件,包括用于增强和自定义模型的多项功能。 它负责根据用户的输入和可用操作生成和执行计划。

示例代码参考

    // Create model
    
    OpenAIModel? model = null;
    
    if (!string.IsNullOrEmpty(config.OpenAI?.ApiKey))
    {
        model = new(new OpenAIModelOptions(config.OpenAI.ApiKey, "gpt-3.5-turbo"));
    }
    else if (!string.IsNullOrEmpty(config.Azure?.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint))
    {
        model = new(new AzureOpenAIModelOptions(
            config.Azure.OpenAIApiKey,
            "gpt-35-turbo",
            config.Azure.OpenAIEndpoint
        ));
    }
    
    if (model == null)
    {
        throw new Exception("please configure settings for either OpenAI or Azure");
    }

    // Create prompt manager
    PromptManager prompts = new(new()
    {
        PromptFolder = "./Prompts",
    });

    // Add function to be referenced in the prompt template

    prompts.AddFunction("getLightStatus", async (context, memory, functions, tokenizer, args) =>
    {
        bool lightsOn = (bool)(memory.GetValue("conversation.lightsOn") ?? false);
        return await Task.FromResult(lightsOn ? "on" : "off");
    });

    // Create ActionPlanner
    ActionPlanner<AppState> planner = new(
        options: new(
            model: model,
            prompts: prompts,
            defaultPrompt: async (context, state, planner) =>
            {
                PromptTemplate template = prompts.GetPrompt("sequence");
                return await Task.FromResult(template);
            }
        )
        { LogRepairs = true },
        loggerFactory: loggerFactory
    );

定义存储和应用程序

应用程序对象自动管理机器人的聊天和用户状态。

  • 存储:Create存储提供程序来存储机器人的对话和用户状态。

  • 应用程序:应用程序类包含应用所需的所有信息和机器人逻辑。 可以在此类中为应用注册操作或活动处理程序。

示例代码参考

 return new TeamsLightBot(new()
    {
        Storage = sp.GetService<IStorage>(),
        AI = new(planner),
        LoggerFactory = loggerFactory,
        TurnStateFactory = () =>
        {
            return new AppState();
        }
    });

TurnStateFactory 允许为应用程序创建自定义状态类。 可以使用它来存储机器人所需的其他信息或逻辑。 还可以替代轮次状态的一些默认属性,例如用户输入、机器人输出或对话历史记录。 若要使用 TurnStateFactory,需要创建一个扩展默认轮次状态的类,并将创建类实例的函数传递给应用程序构造函数。

注册数据源

矢量数据源可以轻松地将 RAG 添加到任何提示。 可以将命名数据源注册到规划器,然后指定数据源的名称,以增强提示文件中的 config.json 提示。 数据源允许 AI 将来自外部源的相关信息注入提示,例如矢量数据库或认知搜索。 可以将命名数据源注册到规划器,然后指定要在提示 config.json 文件中增加提示符的数据源的名称。

示例代码参考

// Register your data source with planner
planner.prompts.addDataSource(new VectraDataSource({
    name: 'teams-ai',
    apiKey:  process.env.OPENAI_API_KEY!,
    indexFolder: path.join(__dirname, '../index'),
}));

嵌入

嵌入是 LLM 生成的一种矢量,表示一段文本。 文本可以是单词、句子或整个文档。 由于模型理解语言的语法和语义,嵌入可以捕获紧凑形式的文本的语义含义。 嵌入通常用于自然语言处理任务(如文本分类或情绪分析),但也用于搜索。

用于生成嵌入的模型不同于基础 LLM。 例如,OpenAI 提供了一个名为 text-embedding-ada-002 的嵌入模型,该模型返回表示输入文本的 1536 个数字的列表。 系统为文档中的文本创建嵌入,并将其存储在矢量数据库中。 现在,通过聊天应用程序,我们可以实现 RAG 模式,方法是首先从矢量数据库中检索有关文档的相关数据,然后使用此检索到的信息扩充提示。


下面是 VectraDataSource 和 OpenAIEmbeddings 的示例:
import { DataSource, Memory, RenderedPromptSection, Tokenizer } from '@microsoft/teams-ai';
import { OpenAIEmbeddings, LocalDocumentIndex } from 'vectra';
import * as path from 'path';
import { TurnContext } from 'botbuilder';

/**
 * Options for creating a `VectraDataSource`.
 */
export interface VectraDataSourceOptions {
    /**
     * Name of the data source and local index.
     */
    name: string;

    /**
     * OpenAI API key to use for generating embeddings.
     */
    apiKey: string;

    /**
     * Path to the folder containing the local index.
     * @remarks
     * This should be the root folder for all local indexes and the index itself
     * needs to be in a subfolder under this folder.
     */
    indexFolder: string;

    /**
     * Optional. Maximum number of documents to return.
     * @remarks
     * Defaults to `5`.
     */
    maxDocuments?: number;

    /**
     * Optional. Maximum number of chunks to return per document.
     * @remarks
     * Defaults to `50`.
     */
    maxChunks?: number;

    /**
     * Optional. Maximum number of tokens to return per document.
     * @remarks
     * Defaults to `600`.
     */
    maxTokensPerDocument?: number;
}

/**
 * A data source that uses a local Vectra index to inject text snippets into a prompt.
 */
export class VectraDataSource implements DataSource {
    private readonly _options: VectraDataSourceOptions;
    private readonly _index: LocalDocumentIndex;

    /**
     * Name of the data source.
     * @remarks
     * This is also the name of the local Vectra index.
     */
    public readonly name: string;

    /**
     * Creates a new `VectraDataSource` instance.
     * @param options Options for creating the data source.
     */
    public constructor(options: VectraDataSourceOptions) {
        this._options = options;
        this.name = options.name;

        // Create embeddings model
        const embeddings = new OpenAIEmbeddings({
            model: 'text-embedding-ada-002',
            apiKey: options.apiKey,
        });

        // Create local index
        this._index = new LocalDocumentIndex({
            embeddings,
            folderPath: path.join(options.indexFolder, options.name),
        });
    }

    /**
     * Renders the data source as a string of text.
     * @param context Turn context for the current turn of conversation with the user.
     * @param memory An interface for accessing state values.
     * @param tokenizer Tokenizer to use when rendering the data source.
     * @param maxTokens Maximum number of tokens allowed to be rendered.
     */
    public async renderData(context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number): Promise<RenderedPromptSection<string>> {
        // Query index
        const query = memory.getValue('temp.input') as string;
        const results = await this._index.queryDocuments(query, {
            maxDocuments: this._options.maxDocuments ?? 5,
            maxChunks: this._options.maxChunks ?? 50,
        });

        // Add documents until you run out of tokens
        let length = 0;
        let output = '';
        let connector = '';
        for (const result of results) {
            // Start a new doc
            let doc = `${connector}url: ${result.uri}\n`;
            let docLength = tokenizer.encode(doc).length;
            const remainingTokens = maxTokens - (length + docLength);
            if (remainingTokens <= 0) {
                break;
            }

            // Render document section
            const sections = await result.renderSections(Math.min(remainingTokens, this._options.maxTokensPerDocument ?? 600), 1);
            docLength += sections[0].tokenCount;
            doc += sections[0].text;

            // Append do to output
            output += doc;
            length += docLength;
            connector = '\n\n';
        }

        return { output, length, tooLong: length > maxTokens };
    }

}

Prompt

提示是可用于创建对话体验的文本片段。 提示用于启动对话、提问和生成响应。 使用提示有助于降低创建对话体验的复杂性,并使它们对用户更具吸引力。

基于对象的新提示系统将提示分解为部分,并且每个部分都可以获得一个令牌预算,该预算是一组固定的令牌,或者与整个剩余令牌成比例。 可以为文本完成和聊天完成样式 API 生成提示。

下面是创建提示的一些准则:

  • 提供说明和/或示例。
  • 提供质量数据。 确保有足够的示例并校对示例。 该模型非常智能,可以查看基本拼写错误并给出响应,但它也可能假定输入是有意的,并且可能会影响响应。
  • 检查提示设置。 温度和top_p设置控制模型在生成响应方面的确定性。 较高的值(如 0.8)使输出随机,而较低值(如 0.2)使输出具有焦点和确定性。

Create名为 prompts 的文件夹,并在 文件夹中定义提示。 当用户通过输入文本提示与机器人交互时,机器人会以文本完成进行响应。

  • skprompt.txt:包含提示文本,并支持模板变量和函数。 在 文件中定义所有文本提示 skprompt.txt

  • config.json:包含提示模型设置。 提供正确的配置,以确保机器人响应符合你的要求。

    示例代码参考

     {
        "schema": 1.1,
        "description": "A bot that can turn the lights on and off",
        "type": "completion",
        "completion": {
            "model": "gpt-3.5-turbo",
            "completion_type": "chat",
            "include_history": true,
            "include_input": true,
            "max_input_tokens": 2800,
            "max_tokens": 1000,
            "temperature": 0.2,
            "top_p": 0.0,
            "presence_penalty": 0.6,
            "frequency_penalty": 0.0,
            "stop_sequences": []
        },
        "augmentation": {
            "augmentation_type": "sequence"
            "data_sources": {
                 "teams-ai": 1200
         }
        }
      }
    

查询参数

下表列出了查询参数:

说明
model 要使用的模型的 ID。
completion_type 要用于模型的完成类型。 如果出现提示,模型将返回一个或多个预测完成,以及每个位置的替代令牌的概率。 支持的选项是 chattext。 默认值为“chat”。
include_history Boolean 值。 如果要包含历史记录。 每个提示获取其自己的单独对话历史记录,以确保模型不会混淆。
include_input Boolean 值。 如果要在提示中包含用户的输入。 提示的令牌数。
max_input_tokens 输入的最大令牌数。 支持的最大令牌数为 4000。
max_tokens 完成时要生成的最大令牌数。 提示符的标记计数加上max_tokens不能超过模型的上下文长度。
temperature 要使用的采样温度,介于 0 和 2 之间。 较高的值(如 0.8)会使输出更加随机,而较低的值(如 0.2)使输出更加集中和确定性。
top_p 温度采样的替代方法,称为核采样,其中模型考虑具有top_p概率质量的标记的结果。 因此,0.1 表示仅考虑包含前 10% 概率质量的标记。
presence_penalty 介于 -2.0 和 2.0 之间的数字。 正值会根据新令牌是否出现在文本中,从而增加模型谈论新主题的可能性。
frequency_penalty 介于 -2.0 和 2.0 之间的数字。 正值会根据新标记在文本中的现有频率来惩罚这些标记,从而降低模型逐字重复同一行的可能性。
stop_sequences 最多四个序列,其中 API 停止生成更多令牌。 返回的文本不包含停止序列。
augmentation_type 扩充的类型。 支持的值为 sequencemonologuetools

提示管理

考虑到可用的令牌预算和数据源或扩充,提示管理有助于调整发送到语言模型的提示的大小和内容。

如果机器人最多具有 4,000 个令牌,其中 2,800 个令牌用于输入,1,000 个令牌用于输出,则模型可以管理整个上下文窗口,并确保它永远不会处理超过 3,800 个令牌。 该模型从大约 100 个令牌的文本开始,在另外 1,200 个令牌的数据源中添加 ,然后查看 1,500 个令牌的剩余预算。 系统将剩余的 1,500 个令牌分配给对话历史记录和输入。 然后,将对话历史记录压缩为适合剩余空间,确保模型永远不会超过 2,800 个令牌。

提示操作

计划允许模型执行操作或响应用户。 可以创建计划的架构,并添加支持执行操作和传递参数的操作列表。 OpenAI 终结点找出需要使用的操作,提取所有实体,并将其作为参数传递给操作调用。

The following is a conversation with an AI assistant.
The assistant can turn a light on or off.

context:
The lights are currently {{getLightStatus}}.

提示模板

提示模板是一种使用纯文本定义和撰写 AI 函数的简单而强大的方法。 可以使用提示模板创建自然语言提示、生成响应、提取信息、调用其他提示或执行可以用文本表示的任何其他任务。

语言支持包含变量、调用外部函数以及将参数传递给函数的功能。 无需编写任何代码或导入任何外部库,只需使用大括号 {{...} 以在提示中嵌入表达式。 Teams 分析模板并执行模板背后的逻辑。 这样,你可以以最少的工作量和最大的灵活性轻松地将 AI 集成到应用中。

  • {{function}}:调用已注册的函数并插入其返回值字符串。

  • {{$input}}:插入消息文本。 它从 state.temp.input 获取其值。

  • {{$state.[property]}}:插入状态属性。

操作

操作处理由 AI 组件触发的事件。

FlaggedInputActionFlaggedOutputAction 是用于处理审查器标志的内置操作处理程序。 如果审查器标记了传入消息输入,审查器将重定向到 FlaggedInputAction 处理程序,并 context.sendActivity 向用户发送有关该标志的消息。 如果要停止操作,则必须添加 AI.StopCommandName

示例代码参考

// Register other AI actions
app.ai.action(
    AI.FlaggedInputActionName,
    async (context: TurnContext, state: ApplicationTurnState, data: Record<string, any>) => {
        await context.sendActivity(`I'm sorry your message was flagged: ${JSON.stringify(data)}`);
        return AI.StopCommandName;
    }
);

app.ai.action(AI.FlaggedOutputActionName, async (context: TurnContext, state: ApplicationTurnState, data: any) => {
    await context.sendActivity(`I'm not allowed to talk about such things.`);
    return AI.StopCommandName;
});

注册操作处理程序

操作处理程序可帮助用户实现目标,这些目标在用户意向中共享。

操作处理程序中的一个关键方面是,必须首先在提示中注册操作,然后帮助用户实现目标。

必须为提示中列出的每个操作注册处理程序,并添加处理程序来处理未知操作。

在以下示例的轻型机器人中 LightsOn,我们有 、 LightsOffPause 操作。 每次调用操作时,都会返回 string。 如果需要机器人返回时间,则无需分析时间并将其转换为数字。 属性 PauseParameters 确保它以数字格式返回时间,而不会暂停提示。

示例代码参考

public class LightBotActions
    {
        [Action("LightsOn")]
        public async Task<string> LightsOn([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            turnState.Conversation!.LightsOn = true;
            await turnContext.SendActivityAsync(MessageFactory.Text("[lights on]"));
            return "the lights are now on";
        }

        [Action("LightsOff")]
        public async Task<string> LightsOff([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            turnState.Conversation!.LightsOn = false;
            await turnContext.SendActivityAsync(MessageFactory.Text("[lights off]"));
            return "the lights are now off";
        }

        [Action("Pause")]
        public async Task<string> LightsOff([ActionTurnContext] ITurnContext turnContext, [ActionParameters] Dictionary<string, object> args)
        {
            // Try to parse entities returned by the model.
            // Expecting "time" to be a number of milliseconds to pause.
            if (args.TryGetValue("time", out object? time))
            {
                if (time != null && time is string timeString)
                {
                    if (int.TryParse(timeString, out int timeInt))
                    {
                        await turnContext.SendActivityAsync(MessageFactory.Text($"[pausing for {timeInt / 1000} seconds]"));
                        await Task.Delay(timeInt);
                    }
                }
            }

            return "done pausing";
        }

        [Action("LightStatus")]
        public async Task<string> LightStatus([ActionTurnContext] ITurnContext turnContext, [ActionTurnState] AppState turnState)
        {
            await turnContext.SendActivityAsync(ResponseGenerator.LightStatus(turnState.Conversation!.LightsOn));
            return turnState.Conversation!.LightsOn ? "the lights are on" : "the lights are off";
        }

        [Action(AIConstants.UnknownActionName)]
        public async Task<string> UnknownAction([ActionTurnContext] TurnContext turnContext, [ActionName] string action)
        {
            await turnContext.SendActivityAsync(ResponseGenerator.UnknownAction(action ?? "Unknown"));
            return "unknown action";
        }
    }
}

如果使用 sequencemonologuetools 扩充,则模型不可能幻觉无效的函数名称、操作名称或正确的参数。 必须创建新的操作文件并定义你希望提示支持扩充的所有操作。 必须定义操作以告知模型何时执行该操作。 序列扩充适用于需要多个步骤或复杂逻辑的任务。 独白扩充适用于需要自然语言理解和生成以及更多灵活性和创造力的任务。

在轻型机器人的以下示例中 actions.json ,该文件包含机器人可以执行的所有操作的列表:

[
    {
        "name": "LightsOn",
        "description": "Turns on the lights"
    },
    {
        "name": "LightsOff",
        "description": "Turns off the lights"
    },
    {
        "name": "Pause",
        "description": "Delays for a period of time",
        "parameters": {
            "type": "object",
            "properties": {
                "time": {
                    "type": "number",
                    "description": "The amount of time to delay in milliseconds"
                }
            },
            "required": [
                "time"
            ]
        }
    }
]
  • name:操作的名称。 必填。
  • description:操作的说明。 可选。
  • parameters:添加所需参数的 JSON 架构对象。

反馈循环是模型用于验证、更正或优化问题的答案的响应。 如果使用 sequence 扩充,可以通过以下方式禁用循环以防止任何意外循环:

  • 可以在定义中AIOptionsfalse 设置为 allow_looping?
  • 可以在 文件中将 设置为 max_repair_attempts0index.ts

管理历史记录

可以使用 MaxHistoryMessagesMaxConversationHistoryTokens 参数允许 AI 库自动管理历史记录。

反馈循环

通过反馈循环,可以随着时间的推移监视和改进机器人的交互,从而生成更有效且用户友好的应用程序。 收到的反馈可用于进行调整和改进,确保机器人始终满足用户的需求和期望。

反馈循环包括以下内容:

修复循环:如果模型的响应低于预期,则会触发修复循环。 会话历史记录分支,使系统能够尝试各种解决方案,而不会影响main对话。

验证:验证验证更正的响应。 如果成功通过验证,系统会取消分叉会话,并将修复的结构重新插入到main会话中。

从错误中学习:一旦模型看到正确行为的示例,就会学会避免将来出现类似的错误。

处理复杂命令:一旦模型从错误中吸取了教训,它就能够处理更复杂的命令并返回所需的计划。

后续步骤